package com.qianxun.plugins.shiro;

import com.qianxun.export.constant.HttpCodeEnum;
import com.qianxun.framework.common.util.ObjectUtil;
import com.qianxun.init.properties.ShiroProperties;
import com.qianxun.module.base.constant.Result;
import com.qianxun.module.datasource1.develop.entity.SysFunction;
import com.qianxun.module.datasource1.develop.service.SysFunctionRoleRelationService;
import com.qianxun.module.datasource1.develop.service.SysFunctionService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 系统权限总控
 *
 * @author: huwei
 * @date: 2019/11/27 16:59
 * @version: 1.0.0
 */
@Slf4j
public class PermissionAuthenticationFilter extends AuthorizationFilter {
    private WebApplicationContext webApplicationContext;
    /**
     * 功能角色映射服务对象，用于查询该功能与角色的映射关系
     */
    private SysFunctionRoleRelationService sysAdminFunctionRoleRelationService;
    /**
     * 功能服务对象，用于查询功能信息
     */
    private SysFunctionService sysFunctionService;
    /**
     * shiro配置属性
     */
    private ShiroProperties shiroProperties;
    /**
     * 线程内部变量，记录当前访问被拒绝原因
     */
    private ThreadLocal<AccessDeniedType> accessDeniedTypeThreadLocal = new ThreadLocal();
    /**
     * 当前访问功能对象
     */
    private ThreadLocal<SysFunction> sysFunctionThreadLocal = new ThreadLocal();

    public PermissionAuthenticationFilter(WebApplicationContext webApplicationContext) {
        this.webApplicationContext = webApplicationContext;
        this.sysAdminFunctionRoleRelationService = getService(SysFunctionRoleRelationService.class);
        this.sysFunctionService = getService(SysFunctionService.class);
        this.shiroProperties = getService(ShiroProperties.class);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        String uri = functionUri((HttpServletRequest) request);

        SysFunction sysFunction = sysFunctionService.getOneByFullUri(uri);
        if (sysFunction == null) {
            //功能库中不存在当前请求的uri，该uri有可能是html/js/image等资源，所以直接放行
            return true;
        }
        sysFunctionThreadLocal.set(sysFunction);
        if (sysFunction.getStatus() == 0) {
            //功能已被禁用，拦截
            accessDeniedTypeThreadLocal.set(AccessDeniedType.DISABLE);
            return false;
        }
        if (sysFunction.getPermissionSymbol() == 0) {
            //不需要认证，放行
            return true;
        }
        //用户权限认证
        Subject subject = getSubject(request, response);
        //未登录，拦截
        if (subject.getPrincipal() == null) {
            accessDeniedTypeThreadLocal.set(AccessDeniedType.UN_LOGIN);
            return false;
        }
        //获取当前uri的角色信息
        List<String> functionRoleNameList = sysAdminFunctionRoleRelationService.listFunctionNameByFunctionId(sysFunction.getId());
        if (functionRoleNameList == null || functionRoleNameList.isEmpty()) {
            //无功能角色，只需要用户登录，放行
            return true;
        }
        boolean[] booleans = subject.hasRoles(functionRoleNameList);
        for (boolean b : booleans) {
            if (b) {
                //存在可访问的角色，放行
                return true;
            }
        }
        //无权访问
        accessDeniedTypeThreadLocal.set(AccessDeniedType.RIGHT_LESS);
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        HttpServletRequest request1 = (HttpServletRequest) request;
        HttpServletResponse response1 = (HttpServletResponse) response;
        Object obj = ShiroUtil.getSubject().getPrincipal();
        //用户处于登录状态，且访问页面不是登录页
        if (obj != null && !isLoginUrl(request1)) {
            if (accessDeniedTypeThreadLocal.get().equals(AccessDeniedType.DISABLE)) {
                //资源被禁用
                disableResourceProcess(request1, response1);
                return false;
            } else if (accessDeniedTypeThreadLocal.get().equals(AccessDeniedType.RIGHT_LESS)) {
                //无权利访问
                accessDeniedProcess(request1, response1);
                return false;
            }
        }

        if (sysFunctionThreadLocal.get().getResultType() == 0) {
            //页面数据
            //登录页判断
            List<ShiroProperties.LoginUrlConfig> pageConfigList = shiroProperties.getLoginUrlConfigList();
            // 跳转自定义login page
            if (pageConfigList != null && !pageConfigList.isEmpty()) {
                String requestUri = functionUri(request1);
                for (ShiroProperties.LoginUrlConfig pageConfig : pageConfigList) {
                    if (pathMatcher.matches(pageConfig.getMatchUrl(), requestUri)) {
                        forwardPage(request1, response1, pageConfig.getLoginUrl());
                        return false;
                    }
                }
            }
        } else {
            //json数据
            String json = ObjectUtil.toJson(Result.fail(HttpCodeEnum.AUTH_USER_NOT_LOGIN.getCode(), "请登录"));
            responseJson(response1, json);
            return false;
        }
        return super.onAccessDenied(request, response);
    }

    private boolean isLoginUrl(HttpServletRequest request) {
        List<ShiroProperties.LoginUrlConfig> pageConfigList = shiroProperties.getLoginUrlConfigList();
        if (pageConfigList != null && !pageConfigList.isEmpty()) {
            String requestUri = functionUri(request);
            for (ShiroProperties.LoginUrlConfig pageConfig : pageConfigList) {
                if (requestUri.equals(pageConfig.getLoginUrl())) {
                    return true;
                }
            }
        }
        return false;
    }

    private void disableResourceProcess(HttpServletRequest request, HttpServletResponse response) {
        String disableUrl = shiroProperties.getDisableUrl();
        if (sysFunctionThreadLocal.get().getResultType() == 0) {
            if (StringUtils.isEmpty(disableUrl)) {
                responseHtml(response, "资源已被禁用");
            } else {
                forwardPage(request, response, disableUrl);
            }
        } else {
            String json = ObjectUtil.toJson(Result.fail(HttpCodeEnum.RESOURCE_ACCESS_DENIED.getCode(), "资源已被禁用"));
            responseJson(response, json);
        }
    }

    private void accessDeniedProcess(HttpServletRequest request, HttpServletResponse response) {
        String accessDeniedUrl = shiroProperties.getAccessDeniedUrl();
        if (sysFunctionThreadLocal.get().getResultType() == 0) {
            if (StringUtils.isEmpty(accessDeniedUrl)) {
                responseHtml(response, "无权访问");
            } else {
                forwardPage(request, response, accessDeniedUrl);
            }
        } else {
            String json = ObjectUtil.toJson(Result.fail(HttpCodeEnum.PERMISSION_ACCESS_DENIED.getCode(), "无权访问"));
            responseJson(response, json);
        }
    }

    private void responseHtml(HttpServletResponse response, String html) {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=UTF-8");
        try {
            IOUtils.write(html, response.getWriter());
        } catch (IOException e) {
            log.error("返回资源已被禁用时，错误", e);
        }
    }

    private void responseJson(HttpServletResponse response, String json) {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=UTF-8");
        try {
            IOUtils.write(json, response.getWriter());
        } catch (IOException e) {
            log.error("返回资源已被禁用时，错误", e);
        }
    }

    private void forwardPage(HttpServletRequest request, HttpServletResponse response, String pageUrl) {
        //使用转发，防止该请求被设置权限，重定向造成无限次重定向；
        try {
            request.getRequestDispatcher(pageUrl).forward(request, response);
        } catch (ServletException | IOException e) {
            log.error("请求转发到：" + pageUrl + "页失败", e);
        }
    }


    private String functionUri(HttpServletRequest request) {
        String uri = request.getRequestURI();
        String contextPath = request.getContextPath();
        return uri.substring(contextPath.length());
    }

    public <T> T getService(Class<T> cls) {
        Object obj = ServiceHolder.service.get(cls.getName());
        if (obj != null && obj.getClass().isAssignableFrom(cls)) {
            return (T) obj;
        }
        T t = webApplicationContext.getBean(cls);
        ServiceHolder.service.put(cls.getName(), t);
        return t;
    }

    private static class ServiceHolder {
        static final Map<String, Object> service = new HashMap();
    }

    private enum AccessDeniedType {
        UN_LOGIN, DISABLE, RIGHT_LESS;
    }
}
